package org.zoomdev.zoom.rpc.server;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.zoomdev.zoom.aop.Interceptor;
import org.zoomdev.zoom.aop.Invoker;
import org.zoomdev.zoom.aop.factory.MethodInvokerFactory;
import org.zoomdev.zoom.aop.impl.InterceptorChan;
import org.zoomdev.zoom.common.exceptions.ZoomException;
import org.zoomdev.zoom.rpc.HeartbeatService;
import org.zoomdev.zoom.rpc.MethodId;
import org.zoomdev.zoom.rpc.utils.RpcUtils;
import org.zoomdev.zoom.serializer.Serializer;
import org.zoomdev.zoom.serializer.SerializerFactory;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ServiceContainer implements ServiceInvoker {

    private static final Log log = LogFactory.getLog(ServiceContainer.class);

    private final Map<String, ServiceInfo> serviceCache = new ConcurrentHashMap<>();
    private final MethodInvokerFactory factory = new MethodInvokerFactory();
    private final List<Handler> handlers = new ArrayList<>();
    private final SerializerFactory serializerFactory;
    private final Serializer commonSerializer;

    private final List<MethodId> ids = new ArrayList<>();

    private final Serializer<List<MethodId>> serializer;

    public ServiceContainer(SerializerFactory serializerFactory) {
        this.serializerFactory = serializerFactory;
        this.commonSerializer = serializerFactory.create(null);
        this.serializer = this.serializerFactory.create(RpcUtils.listType(MethodId.class));
        register(HeartbeatService.class, (HeartbeatService) i -> i);
    }

    @Override
    public Command getMethodIds() {
        Command command = new Command();
        command.resultBytes = serializer.serialize(ids);
        return command;
    }

    public ServiceInfo register(Class<?> type, Object instance) {
        synchronized (this) {
            String className = type.getName();
            if (serviceCache.containsKey(className)) {
                throw new ZoomException("服务已经存在，请勿重复注册");
            }

            List<Method> methods = RpcUtils.findPublicMethods(type);
            //将methods排序
            methods.sort(new Comparator<Method>() {
                @Override
                public int compare(Method o1, Method o2) {
                    return o1.toGenericString().compareTo(o2.toGenericString());
                }
            });

            for (Method method : methods) {
                try {
                    Invoker invoker = factory.create(method);
                    if (interceptor != null) {
                        invoker = new InterceptorChan(interceptor, invoker, method);
                    }
                    Serializer argumentSerializer = serializerFactory.createWithTypes(method.getGenericParameterTypes());
                    Serializer serializer = serializerFactory.create(method.getGenericReturnType());
                    Handler handler = new SimpleHandler(invoker, instance, serializer, argumentSerializer, method.toGenericString());

                    this.ids.add(new MethodId(method.toGenericString(), (short) handlers.size()));
                    handlers.add(handler);
                } catch (Throwable e) {
                    log.warn("创建rpc服务方法:" + method + " 产生异常", e);
                    if (e instanceof RuntimeException) {
                        throw (RuntimeException) e;
                    }
                    throw new ZoomException(e);
                }
            }

            ServiceInfo info = new ServiceInfo();
            info.type = type;
            info.instance = instance;
            info.name = className;
            info.version = RpcUtils.getVersion(methods);
            serviceCache.put(className, info);
            return info;
        }
    }


    @Override
    public void invoke(Command command) {
        Handler handler = handlers.get(command.func);
        byte[] result;
        short retFunc;
        try {
            result = handler.call(command.bytes);
            retFunc = command.func;
        } catch (Throwable t) {
            result = commonSerializer.serialize(t);
            retFunc = RpcUtils.FUNC_ERROR;
        }

        command.setResult(retFunc, result);
    }


    private Interceptor interceptor;

    public void setInterceptor(Interceptor interceptor) {
        this.interceptor = interceptor;
    }
}
